﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Windows.Controls;
using System.Windows.Data;
using Xceed.Silverlight.Data;

namespace Xceed.Silverlight.DataGrid
{
  internal sealed class EditableRowViewModel : WeakViewModelBase
  {
    #region Constructor

    internal EditableRowViewModel( DataPath dataPath, object dataItem, bool isNewItem )
    {
      if( dataPath == null )
        throw new ArgumentNullException( "dataPath" );

      if( dataItem == null )
        throw new ArgumentNullException( "dataItem" );

      m_dataPath = dataPath;
      m_dataItem = dataItem;
      m_isNewItem = isNewItem;

      m_validationErrors = new ValidationErrorsObservableCollection<RowValidationError>();
      m_readOnlyValidationErrors = new ReadOnlyObservableCollection<RowValidationError>( m_validationErrors );

      m_cellsValidationErrors = new ValidationErrorsObservableCollection<CellValidationError>();
      m_readOnlyCellsValidationErrors = new ReadOnlyObservableCollection<CellValidationError>( m_cellsValidationErrors );
    }

    #endregion

    #region DataPath Property

    internal DataPath DataPath
    {
      get
      {
        return m_dataPath;
      }
    }

    private readonly DataPath m_dataPath;

    #endregion

    #region DataItem Property

    internal object DataItem
    {
      get
      {
        return m_dataItem;
      }
    }

    private readonly object m_dataItem;

    #endregion

    #region IsNewItem Property

    internal bool IsNewItem
    {
      get
      {
        return m_isNewItem;
      }
    }

    private readonly bool m_isNewItem;

    #endregion

    #region ValidationErrors Property

    internal ReadOnlyObservableCollection<RowValidationError> ValidationErrors
    {
      get
      {
        return m_readOnlyValidationErrors;
      }
    }

    private readonly ReadOnlyObservableCollection<RowValidationError> m_readOnlyValidationErrors;
    private readonly ValidationErrorsObservableCollection<RowValidationError> m_validationErrors;

    #endregion

    #region CellsValidationErrors Property

    internal ReadOnlyObservableCollection<CellValidationError> CellsValidationErrors
    {
      get
      {
        return m_readOnlyCellsValidationErrors;
      }
    }

    private readonly ReadOnlyObservableCollection<CellValidationError> m_readOnlyCellsValidationErrors;
    private readonly ValidationErrorsObservableCollection<CellValidationError> m_cellsValidationErrors;

    #endregion

    #region HasValidationErrors Property

    internal bool HasValidationErrors
    {
      get
      {
        return ( this.ValidationErrors.Count > 0 );
      }
    }

    #endregion

    #region HasRestrictiveValidationErrors Property

    internal bool HasRestrictiveValidationErrors
    {
      get
      {
        return ( this.ValidationErrors.Any( ( item ) => item.IsRestrictive ) );
      }
    }

    #endregion

    #region HasCellsValidationErrors Property

    internal bool HasCellsValidationErrors
    {
      get
      {
        return ( this.CellsValidationErrors.Count > 0 );
      }
    }

    #endregion

    #region HasCellsRestrictiveValidationErrors Property

    internal bool HasCellsRestrictiveValidationErrors
    {
      get
      {
        return ( this.CellsValidationErrors.Any( ( item ) => item.IsRestrictive ) );
      }
    }

    #endregion

    #region EditingState Property

    internal RowEditingState EditingState
    {
      get
      {
        return m_editingState;
      }
      set
      {
        if( value != m_editingState )
        {
          if( !Enum.IsDefined( typeof( RowEditingState ), value ) )
            throw new ArgumentException( "The EditingState is not defined.", "value" );

          m_editingState = value;

          this.RaisePropertyChanged( () => this.EditingState );
          this.RaisePropertyChanged( () => this.IsBeingEdited );
          this.RaiseKeepAliveChanged();
        }
      }
    }

    private RowEditingState m_editingState = RowEditingState.Unchanged;

    #endregion

    #region IsBeingEdited Property

    internal bool IsBeingEdited
    {
      get
      {
        var currentState = this.EditingState;

        return ( currentState == RowEditingState.Edited )
            || ( currentState == RowEditingState.Ending )
            || ( currentState == RowEditingState.Committing )
            || ( currentState == RowEditingState.Canceling );
      }
    }

    #endregion

    #region Cells Property

    internal IEnumerable<EditableCellViewModel> Cells
    {
      get
      {
        HashSet<EditableCellViewModel> collection = new HashSet<EditableCellViewModel>( m_cells.Values );

        foreach( var cell in this.EditedCells )
        {
          if( !collection.Contains( cell ) )
          {
            collection.Add( cell );
          }
        }

        return collection;
      }
    }

    #endregion

    #region EditedCells Private Property

    private IEnumerable<EditableCellViewModel> EditedCells
    {
      get
      {
        return m_editedCells.Values;
      }
    }

    #endregion

    protected override bool StayAlive()
    {
      return ( this.EditingState != RowEditingState.Unchanged )
          || ( this.HasRestrictiveValidationErrors )
          || ( this.Cells.Any( ( item ) => item.KeepAlive ) )
          || ( base.StayAlive() );
    }

    protected override void OnInitialize()
    {
      base.OnInitialize();

      m_validationErrors.CollectionChanged += new NotifyCollectionChangedEventHandler( this.OnValidationErrorsCollectionChanged );
      m_cellsValidationErrors.CollectionChanged += new NotifyCollectionChangedEventHandler( this.OnCellsValidationErrorsCollectionChanged );

      var validationErrorsCollection = ( INotifyCollectionChanged )m_rowContent.ValidationErrors;
      validationErrorsCollection.CollectionChanged += new NotifyCollectionChangedEventHandler( this.OnRowValidationErrorsCollectionChanged );

      m_rowContent.SetContentBinding( this.GetRowContentBinding() );
    }

    protected override void OnReleasing()
    {
      var columns = ( from cell in m_cells.Values
                      select cell.Column ).ToList();

      foreach( var column in columns )
      {
        this.ReleaseCell( column );
      }

      base.OnReleasing();
    }

    protected override void OnClear()
    {
      m_validationErrors.Clear();
      m_validationErrors.CollectionChanged -= new NotifyCollectionChangedEventHandler( this.OnValidationErrorsCollectionChanged );

      m_cellsValidationErrors.Clear();
      m_cellsValidationErrors.CollectionChanged -= new NotifyCollectionChangedEventHandler( this.OnCellsValidationErrorsCollectionChanged );

      var validationErrorsCollection = ( INotifyCollectionChanged )m_rowContent.ValidationErrors;
      validationErrorsCollection.CollectionChanged -= new NotifyCollectionChangedEventHandler( this.OnRowValidationErrorsCollectionChanged );

      m_validationErrorsMapping.Clear();

      m_rowContent.SetContentBinding( null );

      var editedCells = m_editedCells.Values.ToList();
      m_editedCells.Clear();
      foreach( var cell in editedCells )
      {
        if( !m_cells.ContainsKey( cell.Column ) )
        {
          this.ClearCell( cell );
        }
      }

      var cells = m_cells.Values.ToList();
      m_cells.Clear();
      foreach( var cell in cells )
      {
        this.ClearCell( cell );
      }

      base.OnClear();
    }

    internal EditableCellViewModel GetCell( Column column )
    {
      Debug.Assert( !this.IsCleared );

      EditableCellViewModel cell;
      if( m_editedCells.TryGetValue( column, out cell ) )
        return cell;

      if( m_cells.TryGetValue( column, out cell ) )
        return cell;

      cell = this.CreateCell( column );
      if( cell == null )
        throw new DataGridInternalException();

      m_cells.Add( column, cell );
      this.PrepareCell( cell );

      return cell;
    }

    internal void ReleaseCell( Column column )
    {
      EditableCellViewModel cell;
      if( !m_cells.TryGetValue( column, out cell ) )
        return;

      m_cells.Remove( column );

      this.KeepClearCell( cell );
    }

    internal bool PrepareValues( IEditableCollectionView collection )
    {
      var dataItem = this.DataItem;

      if( collection != null )
      {
        if( collection.IsAddingNew )
          return ( collection.CurrentAddItem == dataItem );

        if( collection.IsEditingItem )
          return ( collection.CurrentEditItem == dataItem );

        collection.EditItem( dataItem );
      }
      else
      {
        IEditableObject item = dataItem as IEditableObject;
        if( item != null )
        {
          item.BeginEdit();
        }
      }

      this.CheckValidationErrors();

      return true;
    }

    internal bool CommitValues( IEditableCollectionView collection )
    {
      foreach( var cell in this.EditedCells.ToList() )
      {
        cell.CommitValue();
      }

      var dataItem = this.DataItem;

      if( collection != null )
      {
        if( collection.IsAddingNew )
        {
          if( collection.CurrentAddItem != dataItem )
            return false;

          collection.CommitNew();
        }
        else if( collection.IsEditingItem )
        {
          if( collection.CurrentEditItem != dataItem )
            return false;

          collection.CommitEdit();
        }
      }
      else
      {
        IEditableObject item = dataItem as IEditableObject;
        if( item != null )
        {
          item.EndEdit();
        }
      }

      this.CheckValidationErrors();

      return true;
    }

    internal bool RollbackValues( IEditableCollectionView collection )
    {
      var dataItem = this.DataItem;

      if( collection != null )
      {
        if( collection.IsAddingNew )
        {
          if( collection.CurrentAddItem != dataItem )
            return false;

          collection.CancelNew();
        }
        else if( collection.IsEditingItem )
        {
          if( !collection.CanCancelEdit || ( collection.CurrentEditItem != dataItem ) )
            return false;

          collection.CancelEdit();
        }
      }
      else
      {
        IEditableObject item = this.DataItem as IEditableObject;
        if( item != null )
        {
          item.CancelEdit();
        }
      }

      foreach( var cell in this.EditedCells.ToList() )
      {
        cell.RollbackValue();
      }

      this.CheckValidationErrors();

      return true;
    }

    private EditableCellViewModel CreateCell( Column column )
    {
      return new EditableCellViewModel( this, column );
    }

    private void PrepareCell( EditableCellViewModel cell )
    {
      this.SubscribeValidationErrors( cell );

      cell.PropertyChanged += new PropertyChangedEventHandler( this.OnCellPropertyChanged );
      cell.Releasing += new EventHandler( this.OnCellReleasing );
      cell.Initialize();
    }

    private void ClearCell( EditableCellViewModel cell )
    {
      this.UnsubscribeValidationErrors( cell );

      cell.PropertyChanged -= new PropertyChangedEventHandler( this.OnCellPropertyChanged );
      cell.Releasing -= new EventHandler( this.OnCellReleasing );
      cell.Clear();
    }

    private void SubscribeValidationErrors( EditableCellViewModel cell )
    {
      INotifyCollectionChanged collection = cell.ValidationErrors;

      m_validationErrorsMapping.Add( collection, cell );

      collection.CollectionChanged += new NotifyCollectionChangedEventHandler( this.OnCellValidationErrors );
    }

    private void UnsubscribeValidationErrors( EditableCellViewModel cell )
    {
      INotifyCollectionChanged collection = cell.ValidationErrors;

      m_validationErrorsMapping.Remove( collection );

      collection.CollectionChanged -= new NotifyCollectionChangedEventHandler( this.OnCellValidationErrors );

      this.RemoveValidationErrors( cell );
    }

    private void KeepClearCell( EditableCellViewModel cell )
    {
      var key = cell.Column;

      if( cell.KeepAlive )
      {
        if( m_editedCells.ContainsKey( key ) )
          return;

        m_editedCells.Add( key, cell );
      }
      else
      {
        m_editedCells.Remove( key );

        if( !m_cells.ContainsKey( key ) )
        {
          this.ClearCell( cell );
          this.RaiseKeepAliveChanged();
        }
      }
    }

    private Binding GetRowContentBinding()
    {
      var binding = new Binding( "." );

      binding.Mode = BindingMode.OneWay;
      binding.NotifyOnValidationError = true;
      binding.ValidatesOnDataErrors = true;
      binding.ValidatesOnExceptions = true;
#if SILVERLIGHT
      binding.ValidatesOnNotifyDataErrors = true;
#endif

      binding.Source = this.DataItem;

      return binding;
    }

    private void RemoveValidationErrors( EditableCellViewModel cell )
    {
      using( m_cellsValidationErrors.Batch() )
      {
        var column = cell.Column;
        for( int i = m_cellsValidationErrors.Count - 1; i >= 0; i-- )
        {
          if( m_cellsValidationErrors[ i ].Column == column )
          {
            m_cellsValidationErrors.RemoveAt( i );
          }
        }
      }

      this.CheckValidationErrors();
    }

    private void UpdateValidationErrors( INotifyCollectionChanged collection )
    {
      EditableCellViewModel cell;
      if( !m_validationErrorsMapping.TryGetValue( collection, out cell ) )
        return;

      using( m_cellsValidationErrors.Batch() )
      {
        this.RemoveValidationErrors( cell );

        foreach( var error in cell.ValidationErrors )
        {
          m_cellsValidationErrors.Add( error );
        }
      }
    }

    private void CheckValidationErrors()
    {
      m_rowContent.RefreshValidationErrors();
    }

    private void OnRowValidationErrorsCollectionChanged( object sender, NotifyCollectionChangedEventArgs e )
    {
      var validationErrors = ( IEnumerable<ValidationError> )sender;

      if( validationErrors.Any() || ( m_validationErrors.Count != 0 ) )
      {
        using( m_validationErrors.Batch() )
        {
          m_validationErrors.Clear();

          foreach( var validationError in validationErrors )
          {
            m_validationErrors.Add( new RowValidationError( this.DataPath, this.DataItem, validationError ) );
          }
        }
      }
    }

    private void OnCellPropertyChanged( object sender, PropertyChangedEventArgs e )
    {
      EditableCellViewModel cell = ( EditableCellViewModel )sender;

      if( e.IsPropertyChanged( ( EditableCellViewModel item ) => cell.KeepAlive ) )
      {
        this.KeepClearCell( cell );
      }
    }

    private void OnCellReleasing( object sender, EventArgs e )
    {
      EditableCellViewModel cell = ( EditableCellViewModel )sender;
      EditableCellViewModel targetCell;
      var key = cell.Column;

      if( !m_cells.TryGetValue( key, out targetCell ) )
      {
        if( !m_editedCells.TryGetValue( key, out targetCell ) )
        {
          targetCell = null;
        }
      }

      if( targetCell == cell )
      {
        this.ReleaseCell( key );
      }
      else
      {
        this.ClearCell( cell );
      }
    }

    private void OnCellValidationErrors( object sender, NotifyCollectionChangedEventArgs e )
    {
      this.UpdateValidationErrors( ( INotifyCollectionChanged )sender );
    }

    private void OnValidationErrorsCollectionChanged( object sender, NotifyCollectionChangedEventArgs e )
    {
      this.RaisePropertyChanged( () => this.HasValidationErrors );
      this.RaisePropertyChanged( () => this.HasRestrictiveValidationErrors );
      this.RaiseKeepAliveChanged();
    }

    private void OnCellsValidationErrorsCollectionChanged( object sender, NotifyCollectionChangedEventArgs e )
    {
      this.RaisePropertyChanged( () => this.HasCellsValidationErrors );
      this.RaisePropertyChanged( () => this.HasCellsRestrictiveValidationErrors );
    }

    #region Private Fields

    private readonly Dictionary<Column, EditableCellViewModel> m_cells = new Dictionary<Column, EditableCellViewModel>();
    private readonly Dictionary<Column, EditableCellViewModel> m_editedCells = new Dictionary<Column, EditableCellViewModel>();
    private readonly Dictionary<INotifyCollectionChanged, EditableCellViewModel> m_validationErrorsMapping = new Dictionary<INotifyCollectionChanged, EditableCellViewModel>();
    private readonly ContentRepository m_rowContent = new ContentRepository();

    #endregion
  }
}
